﻿using System;
using System.Collections.Generic;
using System.Linq;
using EditMode;

namespace ScriptUtilities
{
    public static class EditTime
    {
        public static int DivaTimeFromTimelineIndex(ushort bar, ushort beat, List<BpmChange> bpmChanges, List<TimeSigChange> sigChanges)
        {
            if (bpmChanges.Count < 1)
                return 0;

            float crntBpm = bpmChanges[0].BPM;
            int crntSig = (int)sigChanges[0].Signature + 1;

            double time = 0;
            for (int crntBar = 0; crntBar < bar; crntBar++)
            {
                crntBpm = GetBpmAt(crntBar, bpmChanges);
                crntSig = GetSigAt(crntBar, sigChanges);

                time += (60f / crntBpm) * (crntSig + 1);
            }

            crntBpm = GetBpmAt(bar, bpmChanges);
            crntSig = GetSigAt(bar, sigChanges);
            if (beat != 0)
                time += ((60f / crntBpm) / 192 * (crntSig + 1)) * beat;

            return (int)(time * 100000);
        }

        public static int DivaTimeFromNormalizedTimelineIndex(ushort bar, ushort beat, List<BpmChange> bpmChanges)
        {
            if (bpmChanges.Count < 1)
                return 0;

            TimelineIndex index = new TimelineIndex(bar, beat);

            double crntBpm = bpmChanges[0].BPM;

            double rslt = 0;
            int total = index.TotalBeats;

            int crntTotal = 0;
            for (crntTotal = 0; crntTotal < total; crntTotal += TimelineIndex.BEATS_PER_QUARTER)
            {
                crntBpm = GetBpmAt(TimelineIndex.FromBeats(crntTotal), bpmChanges).BPM;
                rslt += (60.0 / crntBpm);
            }

            if (crntTotal > total)
            {
                var remainingIndex = total - crntTotal;
                rslt += (60.0 / crntBpm / 192.0 * 4.0) * remainingIndex;
            }

            return (int)(rslt * 100000.0);
        }

        private static BpmChange GetBpmAt(TimelineIndex index, List<BpmChange> bpmChanges)
        {
            if (bpmChanges.Count < 1)
                return new BpmChange() { BPM = 0 };

            BpmChange bpmChange = bpmChanges[0];
            for (int i = 0; i < bpmChanges.Count; i++)
            {
                if (new TimelineIndex(bpmChanges[i].BarIndex, bpmChanges[i].BeatIndex) <= index)
                {
                    bpmChange = bpmChanges[i];
                }
                else
                {
                    break;
                }
            }
            return bpmChange;
        }

        private static TimeSigChange GetSigAt(TimelineIndex index, List<TimeSigChange> timeSigChanges)
        {
            if (timeSigChanges.Count < 1)
                return new TimeSigChange() { Signature = TimeSig.Quadruple };

            TimeSigChange timeSigChange = timeSigChanges[0];
            for (int i = 0; i < timeSigChanges.Count; i++)
            {
                if (new TimelineIndex(timeSigChanges[i].BarIndex, timeSigChanges[i].BeatIndex) <= index)
                {
                    timeSigChange = timeSigChanges[i];
                }
                else
                {
                    break;
                }
            }
            return timeSigChange;
        }

        private static float GetBpmAt(int bar, List<BpmChange> bpmChanges)
        {
            if (bpmChanges.Count < 1)
                return 0;

            var bpmChange = bpmChanges.LastOrDefault(b => b.BarIndex <= bar);
            return (bpmChange != null) ? bpmChange.BPM : bpmChanges[0].BPM;
        }

        public static int GetSigAt(int bar, List<TimeSigChange> sigChanges)
        {
            if (sigChanges.Count < 1)
                return 0;

            var sigChange = sigChanges.LastOrDefault(s => s.BarIndex <= bar);
            return (sigChange != null) ? (int)sigChange.Signature : (int)sigChanges[0].Signature;
        }

        public static void NormalizeTimelineIndexes(Edit edit)
        {
            var buffer = new TimeSigChange[edit.TimeSigChanges.Count];
            edit.TimeSigChanges.CopyTo(buffer);
            var originalTimeSigChanges = buffer.ToList();

            foreach (var target in edit.EditTargets)
            {
                var index = NormalizeIndex(new TimelineIndex(target.BarIndex, target.BeatIndex), originalTimeSigChanges);
                target.BarIndex = (ushort)index.Bar;
                target.BeatIndex = (ushort)index.Beat;
                index = NormalizeIndex(new TimelineIndex(target.HoldEndBar, target.HoldEndBeat), originalTimeSigChanges);
                target.HoldEndBar = (ushort)index.Bar;
                target.HoldEndBeat = (ushort)index.Beat;
            }

            foreach (var bpm in edit.BPMChanges)
            {
                var index = NormalizeIndex(new TimelineIndex((int)bpm.BarIndex), originalTimeSigChanges);
                bpm.BarIndex = (uint)index.Bar;
                bpm.BeatIndex = (uint)index.Beat;
            }

            foreach (var sig in edit.TimeSigChanges)
            {
                var index = NormalizeIndex(new TimelineIndex((int)sig.BarIndex), originalTimeSigChanges);
                sig.BarIndex = (uint)index.Bar;
                sig.BeatIndex = (uint)index.Beat;
            }

            TimelineIndex NormalizeIndex(TimelineIndex index, List<TimeSigChange> timeSigChanges)
            {
                int originalTotal = index.TotalBeats;

                int total = 0;
                for (int i = 0; i < index.Bar; i++)
                {
                    int sig = GetSigAt(new TimelineIndex(i), timeSigChanges);
                    total += TimelineIndex.BEATS_PER_QUARTER * (sig + 1);
                }
                total += index.Beat;
                return TimelineIndex.FromBeats(total);
            }

            int GetSigAt(TimelineIndex index, List<TimeSigChange> timeSigChanges)
            {
                if (timeSigChanges.Count < 1)
                    return 0;

                TimeSigChange timeSigChange = timeSigChanges[0];
                for (int i = 0; i < timeSigChanges.Count; i++)
                {
                    if (timeSigChanges[i].BarIndex <= index.Bar)
                    {
                        timeSigChange = timeSigChanges[i];
                    }
                    else
                    {
                        break;
                    }
                }
                return (int)timeSigChange.Signature;
            }
        }
    }

    public struct TimelineIndex
    {
        public const int BEATS_PER_BAR = 192;

        public const int BEATS_PER_QUARTER = BEATS_PER_BAR / 4;

        public static TimelineIndex Zero => new TimelineIndex(0, 0);

        public static TimelineIndex One => new TimelineIndex(1, 0);

        public static TimelineIndex Two => new TimelineIndex(2, 0);

        public int TotalBeats => GetTotalBeats(this);

        public int Bar;

        public int Beat;

        public TimelineIndex(int bar)
        {
            Bar = bar;
            Beat = 0;
        }

        public TimelineIndex(int bar, int beat)
        {
            Bar = bar;
            Beat = beat;
        }

        public TimelineIndex(uint bar, uint beat)
        {
            Bar = (int)bar;
            Beat = (int)beat;
        }

        public static int GetTotalBeats(TimelineIndex index) => (index.Bar * BEATS_PER_BAR) + index.Beat;

        public static TimelineIndex FromBeats(int totalBeat) => new TimelineIndex(totalBeat / BEATS_PER_BAR, totalBeat % BEATS_PER_BAR);

        public static bool operator >(TimelineIndex i0, TimelineIndex i1) => (i0.TotalBeats > i1.TotalBeats);

        public static bool operator <(TimelineIndex i0, TimelineIndex i1) => (i0.TotalBeats < i1.TotalBeats);

        public static bool operator >=(TimelineIndex i0, TimelineIndex i1) => (i0.TotalBeats >= i1.TotalBeats);

        public static bool operator <=(TimelineIndex i0, TimelineIndex i1) => (i0.TotalBeats <= i1.TotalBeats);

        public static TimelineIndex operator +(TimelineIndex i0, TimelineIndex i1) => FromBeats(i0.TotalBeats + i1.TotalBeats);

        public static TimelineIndex operator -(TimelineIndex i0, TimelineIndex i1) => FromBeats(i0.TotalBeats - i1.TotalBeats);

        public static bool operator ==(TimelineIndex obj0, TimelineIndex obj1) => (obj0.TotalBeats == obj1.TotalBeats);

        public static bool operator !=(TimelineIndex obj0, TimelineIndex obj1) => !(obj0 == obj1);

        public override bool Equals(object obj) => base.Equals(obj);

        public override int GetHashCode() => base.GetHashCode();

        public override string ToString() => $"{{{Bar} : {Beat}/{BEATS_PER_BAR}}}";
    }
}
